﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Web.Mvc;
using OperationPlugins.Compositions;
using OperationPlugins.Handlers;
using System.ComponentModel.Composition;

namespace OperationPlugins.Mvc.Filters
{
    /// <summary>
    /// Used to tell that the current action method should invoke plugins handlers, meaning <see cref="IHandler"/> implementations, 
    /// for its parameters.
    /// 
    /// To use this, apply this attribute to a controller class.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public class UsePluginHandlersForAllActionsAttribute : ActionFilterAttribute
    {
        private static IEnumerable<IStaticHandler> StaticHandlers { get; set; }

        private readonly ActionMethodMoment _moments;

        /// <summary>
        /// Initializes a new instance of the UsePluginHandlersForAllActionsAttribute class.
        /// </summary>
        /// <param name="moments">The moment when plugin handlers should be invoked.</param>
        public UsePluginHandlersForAllActionsAttribute(ActionMethodMoment moments)
        {
            _moments = moments;
        }

        /// <summary>
        /// Gets the moment when plugin handlers should be invoked.
        /// </summary>
        public virtual ActionMethodMoment Moments
        {
            get { return _moments; }
        }

        /// <summary>
        /// Gets or sets the current action method moment.
        /// </summary>
        protected virtual ActionMethodMoment CurrentMoment { get; set; }

        /// <summary>
        /// Gets or sets the action method parameters.
        /// </summary>
        protected virtual IDictionary<string, object> ActionParameters { get; set; }

        /// <summary>
        /// Provides the context for the ActionExecuting method of the ActionFilterAttribute class.
        /// </summary>
        protected virtual ActionExecutingContext ActionExecutingContext { get; set; }

        /// <summary>
        /// Provides the context for the ActionExecuted method of the ActionFilterAttribute class.
        /// </summary>
        protected virtual ActionExecutedContext ActionExecutedContext { get; set; }

        /// <summary>
        /// Provides the context for the OnResultExecuting method of the ActionFilterAttribute class.
        /// </summary>
        protected virtual ResultExecutingContext ResultExecutingContext { get; set; }

        /// <summary>
        /// Provides the context for the OnResultExecuted method of the ActionFilterAttribute class.
        /// </summary>
        protected virtual ResultExecutedContext ResultExecutedContext { get; set; }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            CurrentMoment = ActionMethodMoment.OnActionExecuting;
            ActionParameters = filterContext.ActionParameters;
            ActionExecutingContext = filterContext;
            OnCurrentMoment();
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            CurrentMoment = ActionMethodMoment.OnActionExecuted;
            ActionExecutedContext = filterContext;
            OnCurrentMoment();
        }

        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            CurrentMoment = ActionMethodMoment.OnResultExecuting;
            ResultExecutingContext = filterContext;
            OnCurrentMoment();
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            CurrentMoment = ActionMethodMoment.OnResultExecuted;
            ResultExecutedContext = filterContext;
            OnCurrentMoment();
        }

        protected virtual void OnCurrentMoment()
        {
            if (HandleCurrentMoment())
            {
                if (StaticHandlers != null)
                {
                    foreach (var value in GetValuesToHandle())
                    {
                        foreach (IStaticHandler handler in StaticHandlers)
                        {
                            handler.Invoke(value,
                                           ResolveOperation(),
                                           ResolveBranch(),
                                           ResolveOperationContextFactory());                            
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Checks if the current action method moment should be handled.
        /// </summary>
        /// <returns>True if the current action method moment should be handled, otherwise false.</returns>
        protected virtual bool HandleCurrentMoment()
        {
            return (Moments & CurrentMoment) == CurrentMoment;
        }

        /// <summary>
        /// Resolves the current operation.
        /// </summary>
        /// <returns>The current operation.</returns>
        protected virtual OperationId ResolveOperation()
        {
            return OperationPluginsContext.Current.CurrentActionOperation ?? Operations.Any;
        }

        /// <summary>
        /// Resolves the current branch.
        /// </summary>
        /// <returns>The current branch.</returns>
        protected virtual BranchId ResolveBranch()
        {
            return OperationPluginsContext.Current.CurrentActionBranch ?? Branches.Any;
        }

        /// <summary>
        /// Resolves the operation context factory.
        /// </summary>
        /// <returns>The operation context factory.</returns>
        protected virtual IOperationContextFactory ResolveOperationContextFactory()
        {
            return OperationContextManager.GetOperationContextFactory();
        }

        /// <summary>
        /// Gets values to handle.
        /// </summary>
        /// <returns>Values to handle.</returns>
        protected virtual IEnumerable<object> GetValuesToHandle()
        {
            foreach (var param in ActionParameters)
            {
                if (param.Value != null)
                {
                    yield return param.Value;
                }
            }
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        [Export(typeof(IInitialize))]
        private sealed class Initializer : IInitialize
        {
            void IInitialize.Initialize(InitializationContext context)
            {
                StaticHandlers = context.CompositionContainer.GetPlugins<IStaticHandler>();
            }
        }

    }
}
